/*  
 Copyright 2010 The 'Tribe.Blog' development team  
 (http://tribecache.codeplex.com/team/view)

 This library is licensed under the Ms-PL Microsoft Public License.  You should 
 have received a copy of the license along with the source code.  If not, an online copy
 of the license can be found at http://tribecache.codeplex.com/license.
*/

using System;
using System.Reflection;
using System.Threading;
using Tribe.Cache.Enum;
using Tribe.Cache.Factory;
using Tribe.Cache.Helpers;
using Tribe.Cache.Storage;
using Tribe.Cache.Threading;

namespace Tribe.Cache
{
    /// <summary>
    /// Cache
    /// </summary>
    public class Cache : ICache
    {
        [NonSerialized]
        private static readonly ReaderWriterLockSlim CacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        private readonly ICacheFactory _cacheFactory;

        internal CacheTable CacheTable { get; set; }
        /// <summary>
        /// Will instantiate a new singleton instance of the cache table if it it does not already exist
        /// </summary>
        public Cache()
        {
            CacheFactory cacheFactory = new CacheFactory();
            cacheFactory.CreateCache();
            CacheTable = CacheFactory.CacheTable;
        }
        /// <summary>
        /// Used for constructor injection when using an IOC container
        /// </summary>
        /// <param name="cacheFactory">CacheFactory</param>
        public Cache(ICacheFactory cacheFactory)
        {
            _cacheFactory = cacheFactory;
            _cacheFactory.CreateCache();
            CacheTable = CacheFactory.CacheTable;
        }

        /// <summary>
        /// Will either get or put an item into the cache dependant on the objects current state in the cache.
        /// If an object does not exist or has expired then a new instance will be saved to the cache and then returned.
        /// If an object exists and has not expired then the cached instance will be returned
        /// </summary>
        /// <typeparam name="T">Return type - will be inferred from the return type of the delegates encapsulated method</typeparam>
        /// <param name="cacheId">The identifier that will be used to retrieve the object from the cache</param>
        /// <param name="getItemCallback">() => class.Method(params) </param>
        /// <param name="spanToCache">TimeSpan to cache the item for</param>
        /// <returns></returns>
        public T Get<T>(string cacheId, Func<T> getItemCallback, TimeSpan spanToCache)
        {
            return Worker(cacheId, getItemCallback, spanToCache, CacheExpiration.Absolute);
        }
        /// <summary>
        /// Will either get or put an item into the cache dependant on the objects current state in the cache.
        /// If an object does not exist or has expired then a new instance will be saved to the cache and then returned.
        /// If an object exists and has not expired then the cached instance will be returned
        /// </summary>
        /// <typeparam name="T">Return type - will be inferred from the return type of the delegates encapsulated method</typeparam>
        /// <param name="cacheId">The identifier that will be used to retrieve the object from the cache</param>
        /// <param name="getItemCallback">() => class.Method(params) </param>
        /// <param name="spanToCache">TimeSpan to cache the item for</param>
        /// <param name="cacheExpiration">Sets how the item should cache should expire</param>
        /// <returns></returns>
        public T Get<T>(string cacheId, Func<T> getItemCallback, TimeSpan spanToCache, CacheExpiration cacheExpiration)
        {
            return Worker(cacheId, getItemCallback, spanToCache, cacheExpiration);

        }
        /// <summary>
        /// Private method that switches between updating the DateOfExpiration on exit and just returning the object (difference between sliding and absolute)
        /// </summary>
        /// <typeparam name="T">Return type - will be inferred from the return type of the delegates encapsulated method</typeparam>
        /// <param name="cacheId">The identifier that will be used to retrieve the object from the cache</param>
        /// <param name="getItemCallback">() => class.Method(params) </param>
        /// <param name="spanToCache">TimeSpan to cache the item for</param>
        /// <param name="cacheExpiration">Sets how the item should cache should expire</param>
        /// <returns>T</returns>
        private T Worker<T>(string cacheId, Func<T> getItemCallback, TimeSpan spanToCache, CacheExpiration cacheExpiration)
        {
            using (new ReadLock(CacheLock))
            {
                using (new WriteLock(CacheLock))
                {
                    bool hasItemExpired;
                    if (cacheExpiration == CacheExpiration.Absolute)
                    {
                        return (T)(CacheTable.Get(cacheId, out hasItemExpired) ??
                               CacheTable.Put(getItemCallback, spanToCache, cacheId)).Item;
                    }
                    
                    //Sliding
                    return (T)(CacheTable.Get(cacheId, true, out hasItemExpired) ??
                                CacheTable.Put(getItemCallback, spanToCache, cacheId)).Item;
                }
            }
        }
        /// <summary>
        /// Removes an item from the cache
        /// </summary>
        /// <param name="cacheId">The identifier that will be used to remove the object from the cache</param>
        public void Delete(string cacheId)
        {
            using (new WriteLock(CacheLock))
            {
                CacheTable.Delete(cacheId);
            }
        }
        /// <summary>
        /// Clears all items in the cache
        /// </summary>
        public void Clear()
        {
            using (new WriteLock(CacheLock))
            {
                CacheTable.ClearCache();
            }
        }

        /// <summary>
        /// Counts the number of items in the cache
        /// </summary>
        /// <returns>int</returns>
        public int Count()
        {
            using (new ReadLock(CacheLock))
            {
                return CacheTable.Count();
            }
        }
    }
}